#ifndef TCP_IMPL_H
#define TCP_IMPL_H

#include <memory>
#include <thread>
#include <vector>

#include <assert.h>
#include <stdlib.h>

#include "ip4_impl.h"
#include "ip6_impl.h"
#include "framebuf_mgr.h"
#include "common_defs.h"
#include "endpoint_addr.h"

#define MIN_TCP_HDR_SIZE    5

namespace usnet {
class TCPImpl {
public:
    ~TCPImpl()
    {
        FareWell();
        if (transport_.joinable()) {
            transport_.join();
        }
        ip_impl_ = nullptr;
        fb_mgr_ = nullptr;
    }

    static std::unique_ptr<TCPImpl> MakeUnique(
        EndpointAddr* laddr,
        EndpointAddr* raddr,
        const int64_t isn = -1,
        const int32_t rw_flags = RW_DEFAULT);

    int SendData(
        const uint8_t flags,
        const char* data = nullptr,
        const uint32_t len = 0,
        const uint16_t urg = 0);

    int SendData(
        const uint8_t flags,
        const struct iovec*  iov = nullptr,
        const uint32_t iov_len = 0,
        const uint16_t urg = 0);

    int RecvData(char* buf, const uint32_t len);

    int RecvData(struct iovec* iov, const uint32_t iov_len);

    uint32_t RecvData(const struct iovec** iov, const uint32_t size);

    uint32_t Reserve(const struct iovec** iov, const uint32_t size);

    void Discard(const struct iovec* iov);

    bool Commit(const struct iovec* iov);

    inline uint32_t GetHeaderSize() const
    {
        return (hdr_size_ << 2);
    }

    inline uint32_t GetDataSize(const FrameBuf* fb) const
    {
        auto tcp_hdr = fb->tran_hdr_.tcp_hdr_;
        assert(tcp_hdr);
        return fb->tsp_pdu_ - static_cast<uint32_t>(tcp_hdr->hdr_len_ >> 2);
    }
    
private:
    TCPImpl(
        EndpointAddr* laddr,
        EndpointAddr* raddr,
        const int64_t isn = -1,
        const int32_t rw_flags = RW_DEFAULT):
        rw_flags_ { rw_flags },
        laddr_ { laddr },
        raddr_ { raddr }
    {
        srand(time(nullptr));
        tx_syn_seq_ = static_cast<uint32_t>(rand());
        if (isn >= 0) {
            status_ = TCPStatus::LISTENING;
            rx_ack_seq_ = static_cast<uint32_t>(isn);
            rx_syn_seq_ = rx_ack_seq_ + 1;
        }
    }

    inline bool InitIP4Impl()
    {
        ip_impl_ = IP4Impl::MakeUnique(PPT::TCP, laddr_, raddr_);
        if (ip_impl_ == nullptr) {
            return false;
        }
        return true;
    }
    
    inline bool InitIP6Impl()
    {
        ip_impl_ = IP6Impl::MakeUnique(PPT::TCP, laddr_, raddr_);
        if (ip_impl_ == nullptr) {
            return false;
        }
        return true;
    }

    inline void StartTransport()
    {
        transport_ = std::thread(&TCPImpl::Transport, this);
    }

    bool PrefillHeader();
    bool ShakeHands();
    bool FareWell();
    bool SendSyn();
    bool SendSynAck();
    bool RecvSynAck();
    bool SendAck();
    bool RecvAck();
    bool SendFin();
    bool RecvFin();
    void FillSegment(
        FrameBuf* fb,
        const uint8_t flags,
        const char* data = nullptr,
        const uint32_t len = 0,
        const uint16_t urg = 0);
    
    uint16_t CheckSum(const FrameBuf* fb);
    
    inline void Transport()
    {
        while (status_ == TCPStatus::ESTABLISHED) {
            uint32_t rx_data_total = DoRecv();
            DoSend(rx_data_total);
        }
        if (status_ == TCPStatus::CLOSE_WAIT) {
            // pass
        }
    }

    uint32_t DoRecv();
    void DoSend(const uint32_t rx_data_total);
    void HandleTCPFlags(const TCPHeader* tcp_hdr);
    void SortSegment(LinkNode& list_head);

    const int32_t rw_flags_ { RW_DEFAULT };
    // tcp segment data parameters
    EndpointAddr* laddr_  { nullptr };
    EndpointAddr* raddr_  { nullptr };
    uint32_t hdr_size_    { MIN_TCP_HDR_SIZE };   // header size in unit of 32-bits
    
    // sliding window
    // uint32_t cng_wnd_ { };
    // tcp segment sequence parameters
    uint32_t tx_syn_seq_   { };   // [0, tx_syn_seq_) sent
    uint32_t rx_syn_seq_   { };   // [0, rx_syn_seq_) recv
    uint32_t tx_ack_seq_   { };   // [0, tx_ack_seq_) sent acked
    uint32_t rx_ack_seq_   { };   // [0, rx_ack_seq_) recv acked
    LinkNode rx_fb_pend_   { };   // to be consumed
    LinkNode tx_fb_pend_   { };   // to te consumed
    std::thread transport_ { };

    // tcp status
    TCPStatus status_ { TCPStatus::CLOSED };
    std::unique_ptr<BaseIP> ip_impl_        { nullptr };
    std::shared_ptr<FrameBufMgr> fb_mgr_    { nullptr };
};
} // end of namespace usnet
#endif